package com.lbj.mall.search.service.impl;

import com.alibaba.fastjson.JSON;
import com.lbj.mall.common.entity.vo.SkuUpVo;
import com.lbj.mall.search.config.ElasticSearchConfig;
import com.lbj.mall.search.constant.ESConstant;
import com.lbj.mall.search.entity.vo.SearchParams;
import com.lbj.mall.search.entity.vo.SearchResult;
import com.lbj.mall.search.service.MallSearchService;
import lombok.extern.slf4j.Slf4j;
import org.apache.lucene.search.join.ScoreMode;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.NestedQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.index.query.RangeQueryBuilder;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.bucket.MultiBucketsAggregation;
import org.elasticsearch.search.aggregations.bucket.nested.NestedAggregationBuilder;
import org.elasticsearch.search.aggregations.bucket.nested.ParsedNested;
import org.elasticsearch.search.aggregations.bucket.terms.ParsedLongTerms;
import org.elasticsearch.search.aggregations.bucket.terms.ParsedStringTerms;
import org.elasticsearch.search.aggregations.bucket.terms.TermsAggregationBuilder;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightField;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import java.io.IOException;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

@Slf4j
@Service
public class MallSearchServiceImpl implements MallSearchService {

    @Autowired
    RestHighLevelClient client;

    @Override
    public SearchResult search(SearchParams params) {
        SearchResult result = null;
        // 构建搜索的参数
        SearchRequest searchRequest = buildRequestData(params);
        try {
            SearchResponse search = client.search(searchRequest, ElasticSearchConfig.COMMON_OPTIONS);
            // 构建返回数据的结果
            result = buildResponseData(params, search);
        } catch (IOException e) {
            e.printStackTrace();
        }

        return result;
    }

    /**
     * 构建搜索的参数
     * 需要检索的请求：
     * - 模糊匹配关键字
     * - 属性
     * - 分类
     * - 品牌
     * - 价格区间
     * - 库存
     */
    private SearchRequest buildRequestData(SearchParams params) {
        // 构建搜索的所有Builder
        SearchSourceBuilder ssb = new SearchSourceBuilder();

        BoolQueryBuilder boolQ = QueryBuilders.boolQuery();
        // 1.模糊匹配查询skuTitle标题数据
        String keyword = params.getKeyword();
        if (!StringUtils.isEmpty(keyword)) {
            boolQ.must(QueryBuilders.matchQuery("skuTitle", keyword));
        }
        // 2.按照三级分类id来查询
        if (params.getCatalog3Id() != null) {
            boolQ.filter(QueryBuilders.termQuery("catalog3Id", params.getCatalog3Id()));
        }
        // 3.按照品牌id查询
        if (params.getBrandId() != null && params.getBrandId().size() > 0) {
            boolQ.filter(QueryBuilders.termsQuery("brandId", params.getBrandId()));
        }
        // 4.查询是否有库存
        if (params.getHasStock() != null) {
            boolQ.filter(QueryBuilders.termQuery("hasStock", params.getHasStock() == 1));
        }
        // 5.按照价格区间查询 [100_500]
        String skuPrice = params.getSkuPrice();
        if (!StringUtils.isEmpty(skuPrice)) {
            RangeQueryBuilder rangeQ = QueryBuilders.rangeQuery("skuPrice");
            String[] s = skuPrice.split("_");
            if (s.length == 2) {
                rangeQ.gte(s[0]).lte(s[1]);
            } else {
                if (skuPrice.startsWith("_")) {
                    rangeQ.lte(s[0]);
                }
                if (skuPrice.endsWith("_")) {
                    rangeQ.gte(s[0]);
                }
            }
            boolQ.filter(rangeQ);
        }
        // 6.按照属性进行查询
        List<String> attrs = params.getAttrs();
        if (attrs != null && attrs.size() > 0) {
            for (String item : attrs) {
                BoolQueryBuilder bqb = new BoolQueryBuilder();
                // 1_3寸:5寸
                String[] s = item.split("_");
                String attrId = s[0];
                String[] attrValues = s[1].split(":");
                bqb.must(QueryBuilders.termQuery("attrs.attrId", attrId));
                bqb.must(QueryBuilders.termsQuery("attrs.attrValue", attrValues));
                NestedQueryBuilder nestedQ = QueryBuilders.nestedQuery("attrs", bqb, ScoreMode.None);
                boolQ.filter(nestedQ);
            }
        }

        ssb.query(boolQ);

        // 7.排序:saleCount_asc/desc
        String sort = params.getSort();
        if (!StringUtils.isEmpty(sort)) {
            String[] s = sort.split("_");
            ssb.sort(s[0], s[1].equalsIgnoreCase("ASC") ? SortOrder.ASC : SortOrder.DESC);
        }
        // 8.分页
        ssb.from((params.getPage() - 1) * ESConstant.PRODUCT_PAGE_SIZE);
        ssb.size(ESConstant.PRODUCT_PAGE_SIZE);
        // 9.高亮
        if (!StringUtils.isEmpty(keyword)) {
            HighlightBuilder lightBuilder = new HighlightBuilder();
            lightBuilder.field("skuTitle"); // 设置高亮的字段
            lightBuilder.preTags("<b style='color:red'>"); // 设置高亮的字段的前缀
            lightBuilder.postTags("</b>");  // 设置高亮字段的后缀

            ssb.highlighter(lightBuilder);
        }

        // 10.聚合-品牌聚合
        TermsAggregationBuilder brandAgg = AggregationBuilders.terms("brand_agg");
        brandAgg.field("brandId").size(50);
        // 品牌聚合-子聚合-按照名字聚合
        brandAgg.subAggregation(AggregationBuilders.terms("brand_name_agg").field("brandName").size(1));
        // 品牌聚合-子聚合-按照图片聚合
        brandAgg.subAggregation(AggregationBuilders.terms("brand_img_agg").field("brandImg").size(1));
        ssb.aggregation(brandAgg);

        // 11.聚合-分类聚合
        TermsAggregationBuilder catalogAgg = AggregationBuilders.terms("catalog_agg")
                .field("catalogId").size(20);
        catalogAgg.subAggregation(AggregationBuilders.terms("catalog_name_agg")
                .field("catalogName").size(1));
        ssb.aggregation(catalogAgg);

        // 12.聚合-属性聚合
        NestedAggregationBuilder attrAgg = AggregationBuilders.nested("attr_agg", "attr");
        TermsAggregationBuilder attrIdAgg = AggregationBuilders.terms("attr_id_agg").field("attrs.attrId");
        attrIdAgg.subAggregation(AggregationBuilders.terms("attr_name_agg").field("attrs.attrName").size(1));
        attrIdAgg.subAggregation(AggregationBuilders.terms("attr_value_agg").field("attrs.attrName").size(50));
        attrAgg.subAggregation(attrIdAgg);
        ssb.aggregation(attrAgg);

        log.error("-----> " + ssb);

        SearchRequest request = new SearchRequest(new String[]{ESConstant.INDEX_PRODUCT}, ssb);
        return request;
    }

    private SearchResult buildResponseData(SearchParams params, SearchResponse response) {
        SearchResult result = new SearchResult();
        SearchHits hits = response.getHits();

        // 设置当前页码
        result.setCurrentPage(params.getPage());
        // 设置总记录数
        long totalHits = hits.getTotalHits().value;
        result.setTotal(totalHits);
        // 设置总页码数
        int totalPage = (int) totalHits / ESConstant.PRODUCT_PAGE_SIZE;
        result.setTotalPage(totalHits % ESConstant.PRODUCT_PAGE_SIZE == 0 ? totalPage : (totalPage + 1));

        // 设置商品总数
        SearchHit[] resultHit = hits.getHits();
        if (resultHit != null && resultHit.length > 0) {
            List<SkuUpVo> collect = Arrays.stream(resultHit).map(item -> {
                        SkuUpVo skuUpVo = JSON.parseObject(item.getSourceAsString(), SkuUpVo.class);
                        HighlightField highlightField = item.getHighlightFields().get("skuTitle");
                        if (highlightField != null) {
                            String skuTitle = highlightField.fragments()[0].string();
                            skuUpVo.setSkuTitle(skuTitle);
                        }
                        return skuUpVo;
                    }
            ).collect(Collectors.toList());
            result.setProducts(collect);
        }

        // 设置分类信息
        ParsedLongTerms catalogAgg = response.getAggregations().get("catalog_agg");
        List<SearchResult.CatalogVo> catalogCollect = catalogAgg.getBuckets().stream().map(item -> {
            SearchResult.CatalogVo catalogVo = new SearchResult.CatalogVo();
            catalogVo.setCatalogId(Long.parseLong(item.getKeyAsString()));
            ParsedStringTerms catalogNameAgg = item.getAggregations().get("catalog_name_agg");
            String catalogName = catalogNameAgg.getBuckets().get(0).getKeyAsString();
            catalogVo.setCatalogName(catalogName);
            return catalogVo;
        }).collect(Collectors.toList());
        result.setCatalogs(catalogCollect);

        // 设置品牌信息
        ParsedLongTerms brandAgg = response.getAggregations().get("brand_agg");
        List<SearchResult.BrandVo> brandCollect = brandAgg.getBuckets().stream().map(item -> {
            long brandId = item.getKeyAsNumber().longValue();
            String brandName = ((ParsedStringTerms) item.getAggregations().get("brand_name_agg")).getBuckets().get(0).getKeyAsString();
            String brandImg = ((ParsedStringTerms) item.getAggregations().get("brand_img_agg")).getBuckets().get(0).getKeyAsString();
            SearchResult.BrandVo brandVo = new SearchResult.BrandVo();
            brandVo.setBrandId(brandId);
            brandVo.setBrandName(brandName);
            brandVo.setBrandImg(brandImg);
            return brandVo;
        }).collect(Collectors.toList());
        result.setBrands(brandCollect);

        // 设置属性信息

        ParsedNested attrAgg = response.getAggregations().get("attr_agg");
        ParsedLongTerms attrIdAgg = attrAgg.getAggregations().get("attr_id_agg");
        if (attrIdAgg != null) {
            List<SearchResult.AttrVo> attrCollect = attrIdAgg.getBuckets().stream().map(item -> {
                long attrId = item.getKeyAsNumber().longValue();
                String attrName = ((ParsedStringTerms) item.getAggregations().get("attr_name_agg")).getBuckets().get(0).getKeyAsString();
                List<String> attrValues = ((ParsedStringTerms) item.getAggregations().get("attr_value_agg"))
                        .getBuckets().stream().map(MultiBucketsAggregation.Bucket::getKeyAsString).collect(Collectors.toList());
                SearchResult.AttrVo attrVo = new SearchResult.AttrVo();
                attrVo.setAttrId(attrId);
                attrVo.setAttrName(attrName);
                attrVo.setAttrValue(attrValues);
                return attrVo;
            }).collect(Collectors.toList());
            result.setAttrs(attrCollect);
        }
        return result;
    }
}















